﻿using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Xml;
using System.Xml.Serialization;


[assembly: WebResource("AzureRender.AzureDataPart.js", "application/x-javascript")]
namespace AzureRender.AzureDataPart
{
    [ToolboxItemAttribute(true), 
    XmlRoot("AzureRender.AzureDataPart.AzureDataPart")]
    public class AzureDataPart : WebPart
    {

        private const string CAT_NAME = "Connection Properties";
        private const string SCRIPT_KEY = "AzureDataPart";

        private const string DEF_SUCCESS_CALLBACK = "AZUREWCF_azureProcessResults";
        private const string DEF_ERROR_CALLBACK = "AZUREWCF_azureErrorProcessing";
        private const string DEF_ACCESS_DENIED = "<font color='red'>You do not have rights to use this data.</font>";
        private const string DEF_TIMEOUT = "<font color='red'>The request timed out; contact your system administrator if it continues.</font>";

        private const string DEF_RENDER_DIV = "AZUREWCF_azureDefaultDataRenderDiv";
		private const string DEF_SERVERNAME_DIV = "AZUREWCF_azureServerNameRenderDiv";
		private const string DEF_ERRORMSG_DIV = "AZUREWCF_azureErrorMessageRenderDiv";
		private const string DEF_TRACEINFO_DIV = "AZUREWCF_azureTraceInfoRenderDiv";
		private const string DEF_DEBUGPARENT_DIV = "AZUREWCF_azureDebugParentRenderDiv";
		private const string DEF_DIV_IMG = "AZUREWCF_azureDivImageName";
		private const string DEF_DEBUG_FLAG_DIV = "AZUREWCF_azureDebugFlagDiv";


        //method properties
        [WebBrowsable(true), Category(CAT_NAME), 
        Personalizable(PersonalizationScope.Shared),
        WebDescription("Enter the name of the WCF method that should be called. If this " +
            "property is included in the control tag on the DataPage hosting the ASP.NET " + 
            "control, then this property is not required.  If it isn't configured on the " + 
            "DataPage and it isn't set here, executing the call with throw an error."), 
        WebDisplayName("Method Name")]
        public string MethodName { get; set; }


        [WebBrowsable(true), Category(CAT_NAME),
        Personalizable(PersonalizationScope.Shared),
        WebDescription("Enter a semi-colon delimited list of parameter values for the method. " + 
            "The parameters must be ordered the same as the method signature.  If the " +
            "method takes no parameters then leave this property blank."),
        WebDisplayName("Parameter List")]
        public string ParameterList { get; set; }


        [WebBrowsable(true), Category(CAT_NAME),
        Personalizable(PersonalizationScope.Shared),
        WebDescription("Enter the name of your javascript function that should be  " +
            "called with the data after a successful WCF method call.  If this is blank then " +
            "the default SuccessCallbackAddress and display function is used."),
        WebDisplayName("Success Callback Address")]
        public string SuccessCallbackAddress { get; set; }


        [WebBrowsable(true), Category(CAT_NAME),
        Personalizable(PersonalizationScope.Shared),
        WebDescription("Enter the name of your javascript function that should be  " +
            "called when there is an error making the WCF method call.  If this is blank then " +
            "the default ErrorCallbackAddress and display function is used."),
        WebDisplayName("Error Callback Address")]
        public string ErrorCallbackAddress { get; set; }


        [WebBrowsable(true), Category(CAT_NAME),
        Personalizable(PersonalizationScope.Shared),
        WebDescription("Enter the relative Url to the page that contains your control " +
            "that inherits from the WcfConfig base class, for example /_layouts/AzureData.aspx. " +
            "This property is required."),
        WebDisplayName("Data Page")]
        public string DataPage { get; set; }


        [WebBrowsable(true), Category(CAT_NAME),
        Personalizable(PersonalizationScope.Shared),
        WebDescription("You can provide a standard error message to display if there is an error " +
            "on the server rendering this web part.  Note that it IS NOT a message to display " +
            "if there is an error making the request to the WCF service.  To customize that " +
            "please use a custom ErrorCallbackAddress and add your own javascript function."),
        WebDisplayName("Standard Error Message")]
        public string StandardErrorMessage { get; set; }


        [WebBrowsable(true), Category(CAT_NAME),
        Personalizable(PersonalizationScope.Shared),
        WebDescription("This message will be displayed if the user does not have rights to use " +
            "the WCF method, such as if there is a PrincipalPermission demand on the method " +
            "and the user doesn't have the required rights."),
        WebDisplayName("Access Denied Message")]
        public string AccessDeniedMessage { get; set; }


        [WebBrowsable(true), Category(CAT_NAME),
        Personalizable(PersonalizationScope.Shared),
        WebDescription("This message will be displayed if the request times out during " +
            "execution."),
        WebDisplayName("Timeout Message")]
        public string TimeoutMessage { get; set; }


        [WebBrowsable(true), Category(CAT_NAME),
            Personalizable(PersonalizationScope.Shared),
            WebDescription("Check this box to have the web part render a refresh image that " +
                "can be clicked to re-execute the query."),
            WebDisplayName("Show Refresh Link"), DefaultValue(false)]
        public bool ShowRefreshLink { get; set; }


        [WebBrowsable(true), Category(CAT_NAME),
            Personalizable(PersonalizationScope.Shared),
            WebDescription("Use this to add any additional CSS style values to the style " +
                "attribute used on the Refresh Link.  At a minimum, it will always use a " +
                "a style of cursor:hand."),
            WebDisplayName("Refresh Style")]
        public string RefreshStyle { get; set; }


        //doing this property long-hand so we can properly initialize the default value
        private bool m_CacheResults = true;
        [WebBrowsable(true), Category(CAT_NAME),
        Personalizable(PersonalizationScope.Shared),
        WebDescription("Check this box to cache the results of the query, which will avoid " +
            "roundtrips to execute the WCF method.  Data, web part property changes, and " +
            "Data Page changes may not be reflected until closing and reopening the browser."),
        WebDisplayName("Cache Results")]
        public bool CacheResults
        {
            get
            {
                return m_CacheResults;
            }
            set
            {
                m_CacheResults = value;
            }
        }


        [WebBrowsable(true), Category(CAT_NAME),
        Personalizable(PersonalizationScope.Shared),
        WebDescription("If the WCF method is returning encoded results then check this " +
            "box to decode the results that are returned."),
        WebDisplayName("Decode Results"), DefaultValue(false)]
        public bool DecodeResults { get; set; }


		[WebBrowsable(true), Category(CAT_NAME),
		Personalizable(PersonalizationScope.Shared),
		WebDescription("Check this box to have the web part output debugging information " +
			"along with the data to help troubleshoot issues."),
		WebDisplayName("Show Debug Information"), DefaultValue(false)]
		public bool ShowDebugInfo { get; set; }


        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            //set default values
            if (string.IsNullOrEmpty(SuccessCallbackAddress))
                SuccessCallbackAddress = DEF_SUCCESS_CALLBACK;

            if (string.IsNullOrEmpty(ErrorCallbackAddress))
                ErrorCallbackAddress = DEF_ERROR_CALLBACK;

            if (string.IsNullOrEmpty(AccessDeniedMessage))
                AccessDeniedMessage = DEF_ACCESS_DENIED;

            if (string.IsNullOrEmpty(TimeoutMessage))
                TimeoutMessage = DEF_TIMEOUT;
        }

        public override void RenderControl(HtmlTextWriter writer)
        {
            try
            {
                //do the base render
                base.RenderControl(writer);

                //check to see if we're in design mode; if so then we don't want to render
                //the data
                if (this.WebPartManager.DisplayMode == WebPartManager.EditDisplayMode)
                    return;

                //see if the required properties are present; otherwise render a edit web part
                //properties link
                if (string.IsNullOrEmpty(DataPage))
                {
                    string editLink = "<a href=\"javascript:MSOTlPn_ShowToolPane2('Edit', '" + this.ID + "')\">Open tool pane</a> to set the required property DataPage";
                    writer.Write(editLink);
                }
                else
                {
                    //this is the script that executes the request

                    //onclick="AZUREWCF_getAzureData('DataPage.aspx', 'myMethod', '', 
                    //AZUREWCF_azureProcessResults, AZUREWCF_azureErrorProcessing);" />
                    string dataScript = string.Format("AZUREWCF_getAzureData('{0}', '{1}', '{2}', {3}, {4}, '{5}');",
                        DataPage, MethodName, ParameterList, SuccessCallbackAddress,
						ErrorCallbackAddress, this.UniqueID);

                    //render a link to execute the query again if needed
                    if (ShowRefreshLink)
                    {
                        string refreshLink = "<img src=\"/_layouts/images/refresh.gif\" " +
                            "onclick=\"" + dataScript + "\" style=\"cursor:hand;" + 
                            RefreshStyle + "\" /><br/>";
                        writer.Write(refreshLink);
                    }

                    //add the div to the page where we will render the results by default
                    //initially add it with the standard sharepoint waiting gif
                    writer.Write("<div id='" + DEF_RENDER_DIV + this.UniqueID + "'><img src='" +
                        "/_layouts/images/GEARS_AN.GIF'/></div>");

					//flag for displaying debug info
					string debugFlag = "0";

					//write out the debug info details if requested
					if (ShowDebugInfo)
					{
						//add the plus/minus image for showing and hiding debug info
						writer.Write("<p></p><b>Debug Info:</b><br/><img id='" + DEF_DIV_IMG +
							this.UniqueID + 
							"' src='/_layouts/images/plus.gif' onclick='toggleDebugDetailsDiv(\"" + 
							DEF_DIV_IMG + this.UniqueID + "\", \"" + 
							DEF_DEBUGPARENT_DIV + this.UniqueID + "\");' style='cursor:hand;'/>");

						//set the debug flag
						debugFlag = "1";
					}

					//write the debug flag
					writer.Write("<div id='" + DEF_DEBUG_FLAG_DIV + this.UniqueID + "' style='display:none;'>" +
						debugFlag + "</div>");

					//add the master div that holds all the debug info, along with all
					//the child divs that hold each bit of debugging details
					writer.Write("<div id='" + DEF_DEBUGPARENT_DIV + this.UniqueID + "' style='display:none;'>");

					//add each child debug info div
					writer.Write("<b>Server Name:</b><br/><div id='" + DEF_SERVERNAME_DIV + this.UniqueID + "'></div><br/>");
					writer.Write("<b>Error Messages:</b><br/><div id='" + DEF_ERRORMSG_DIV + this.UniqueID + "'></div><br/>");
					writer.Write("<b>Trace Info:</b><br/><div id='" + DEF_TRACEINFO_DIV + this.UniqueID + "'></div>");

					//add the closing tag for the parent div
					writer.Write("</div>");

                    //add the jquery link first
					//<script type="text/javascript" language="javascript" src="jquery-1.5.1.min.js" ></script>
					writer.Write("<script type=\"text/javascript\" language=\"javascript\" src=\"/_layouts/jquery-1.5.1.min.js\" ></script>");

                    //get our js file and do string replacement
                    string js = GetEmbeddedStringResource("AzureRender.AzureDataPart.js");

                    //plug in the variables in the script with the values from the WcfConfig control
					js = js.Replace("##RESULTDIV##", AzureConnect.WcfConfig.RESPONSE_DIV_ID);
					js = js.Replace("##SERVERNAME_DIV##", AzureConnect.WcfConfig.SERVERNAME_DIV_ID);
					js = js.Replace("##ERRORMSG_DIV##", AzureConnect.WcfConfig.ERRORMESSAGE_DIV_ID);
					js = js.Replace("##TRACEINFO_DIV##", AzureConnect.WcfConfig.TRACEINFO_DIV_ID);
					js = js.Replace("##QS_METHOD##", AzureConnect.WcfConfig.QUERYSTRING_METHODNAME);
                    js = js.Replace("##QS_PARAMS##", AzureConnect.WcfConfig.QUERYSTRING_METHODPARAMS);
                    js = js.Replace("##QS_OUTPUT##", AzureConnect.WcfConfig.QUERYSTRING_OUTPUTTYPE);
					js = js.Replace("##QS_DECODE##", AzureConnect.WcfConfig.QUERYSTRING_DECODERESULTS);
                    js = js.Replace("##DECODE_VAL##", DecodeResults.ToString().ToLower());
                    js = js.Replace("##CACHE_OPTION##", CacheResults.ToString().ToLower());
                    js = js.Replace("##QS_ACCESSDENIED##", AzureConnect.WcfConfig.QUERYSTRING_ACCESSDENIEDMSG);
                    js = js.Replace("##AD_VALUE##", AccessDeniedMessage);
                    js = js.Replace("##QS_TIMEOUT##", AzureConnect.WcfConfig.QUERYSTRING_TIMEOUTMSG);
                    js = js.Replace("##TIMEOUT_VALUE##", TimeoutMessage);

					#region Not Needed Anymore
					//these are no longer needed with the change to support multiple parts on a page
					//js = js.Replace("##DEFAULT_RENDER_DIV##", DEF_RENDER_DIV); 
					//js = js.Replace("##SERVERNAME_RESULTS_DIV##", DEF_SERVERNAME_DIV);
					//js = js.Replace("##ERRORMSG_RESULTS_DIV##", DEF_ERRORMSG_DIV);
					//js = js.Replace("##TRACEINFO_RESULTS_DIV##", DEF_TRACEINFO_DIV);
					#endregion

					//write out the script
                    writer.Write(js);

					#region Old Page Load Script
					//NOTE:  THIS SCRIPT WORKS IF THERE'S ONLY ONE PART ON THE PAGE,
					//BUT IF THERE ARE MULTIPLE PARTS THEN ONLY THE LAST PART ADDED
					//TO THE PAGE FIRES ON PAGE LOAD
					////this is the script that executes when the page loads
					//string initScript = "document.onreadystatechange = function () " + 
					//"{ if (document.readyState == \"complete\") " + 
					//"{ " + dataScript + "}   }  ";

					////write out the script to run the method
					//writer.Write("<script type=\"text/javascript\" language=\"javascript\">" +
					//    initScript + "</script>");
					#endregion

					const string START_SCR_KEY = "azurewcf";

					if (!Page.ClientScript.IsStartupScriptRegistered(START_SCR_KEY + this.UniqueID))
						Page.ClientScript.RegisterStartupScript(this.GetType(),
							START_SCR_KEY + this.UniqueID,
							dataScript,
							true);
                }
            }
            catch (Exception ex)
            {
                string errMsg = "There was an error rendering the AzureDataPart Web Part: " +
                    ex.Message;

                AzureConnect.Log.WriteLog(errMsg, AzureConnect.Log.LogTypes.UlsLog,
                    Microsoft.SharePoint.Administration.TraceSeverity.Medium,
                    Microsoft.SharePoint.Administration.EventSeverity.Error);

                //write out either the standard error message if provided or else the raw
                //details captured from the exception
                writer.Write(string.IsNullOrEmpty(StandardErrorMessage) ? errMsg : StandardErrorMessage);
            }
        }


        private string GetEmbeddedStringResource(string ResourceName)
        {
            string value = string.Empty;

            Stream s = null;
            StreamReader sr = null;

            try
            {
                s = Assembly.GetExecutingAssembly().GetManifestResourceStream(ResourceName);
                sr = new StreamReader(s);
                value = sr.ReadToEnd();
            }
            catch (Exception ex)
            {
                //standard error handling here
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                try
                {
                    if (sr != null)
                        sr.Dispose();
                    if (s != null)
                        s.Dispose();
                }
                catch
                {
                    //ignore here, i don't think there's anything we care about in this situation
                }
            }

            return value;
        }
    }
}
